package com.qiqidream.admin.common.utils;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.util.StrUtil;
import com.qiqidream.admin.common.config.JwtConfig;
import com.qiqidream.admin.common.constant.StaticConstants;
import com.qiqidream.admin.common.exception.BaseException;
import com.qiqidream.admin.common.response.ResponseCode;
import com.qiqidream.admin.system.model.entity.User;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author QiQiDream
 * @since 2019/11/25 14:11
 */
@EnableConfigurationProperties(JwtConfig.class)
@Configuration
@Slf4j
public class JwtUtil {
    @Resource
    private JwtConfig jwtConfig;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 创建JWT
     *
     * @param deviceType  设备类型
     * @param rememberMe  记住我
     * @param id           用户id
     * @param subject     用户名
     * @param roles       用户角色
     * @return JWT
     */
    public String createJWT(String deviceType, Boolean rememberMe, User userInfo, Long id, String subject, String roles) {
        Date now = new Date();
        JwtBuilder builder = Jwts.builder()
                .setHeaderParam("type", StaticConstants.TOKEN_TYPE)
                .setId(id.toString())
                .setSubject(subject)
                .setIssuedAt(now)
                .signWith(SignatureAlgorithm.HS256, jwtConfig.getKey())
                .claim("deviceType", deviceType)
                .claim("rememberMe", rememberMe)
                .claim("user", userInfo)
                .claim("roles", roles);


        String jwt = StaticConstants.TOKEN_PREFIX + builder.compact();

        // 设置过期时间
        Long ttl = rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl();
        // 将生成的JWT保存至Redis
        stringRedisTemplate.opsForValue()
                .set(StaticConstants.REDIS_JWT_KEY_PREFIX + deviceType + ":" + subject, jwt, ttl, TimeUnit.MILLISECONDS);
        return jwt;
    }

    /**
     * 创建JWT
     * @param user 用户认证信息
     * @param roleCodes     角色信息
     * @param rememberMe     记住我
     * @return JWT
     */
    public String createJWT(User user,String roleCodes, Boolean rememberMe,String deviceType) {
        return createJWT(deviceType, rememberMe, user, user.getId(), user.getUsername(), roleCodes);
    }

    /**
     * 解析JWT
     *
     * @param jwt JWT
     * @return {@link Claims}
     */
    public Claims parseJWT(String jwt) {

        try {
            Claims claims = Jwts.parser()
                    .setSigningKey(jwtConfig.getKey())
                    .parseClaimsJws(jwt.substring(7))
                    .getBody();
            String deviceType = claims.get("deviceType").toString();
            String username = claims.getSubject();
            String redisKey = StaticConstants.REDIS_JWT_KEY_PREFIX + deviceType + ":" + username;

            // 校验redis中的JWT是否存在
            Long expire = stringRedisTemplate.getExpire(redisKey, TimeUnit.MILLISECONDS);
            if (Objects.isNull(expire) || expire <= 0) {
                throw new BaseException(ResponseCode.TOKEN_EXPIRED);
            }
            // 校验redis中的JWT是否与当前的一致，不一致则代表用户已注销/用户在不同设备登录，均代表JWT已过期
            String redisToken = stringRedisTemplate.opsForValue()
                    .get(redisKey);
            if (!StrUtil.equals(jwt, redisToken)) {
                throw new BaseException(ResponseCode.TOKEN_OUT_OF_CTRL);
            }

            //重置过期时间
            Boolean rememberMe = (Boolean)claims.get("rememberMe");
            Long ttl = rememberMe ? jwtConfig.getRemember() : jwtConfig.getTtl();
            if (ttl > 0) {
                stringRedisTemplate.expire(redisKey, ttl, TimeUnit.MILLISECONDS);
            }

            return claims;
        } catch (ExpiredJwtException e) {
            throw new BaseException(ResponseCode.TOKEN_EXPIRED);
        } catch (UnsupportedJwtException | MalformedJwtException | SignatureException | IllegalArgumentException e) {
            throw new BaseException(ResponseCode.TOKEN_PARSE_ERROR);
        }
    }

    /**
     * 设置JWT过期
     *
     * @param request 请求
     */
    public void invalidateJWT(HttpServletRequest request) {
        String jwt = getJwtFromRequest(request);
        String username = getUsernameFromJWT(jwt);
        // 从redis中清除JWT
        Claims claims = parseJWT(jwt);
        String deviceType = claims.get("deviceType").toString();
        stringRedisTemplate.delete(StaticConstants.REDIS_JWT_KEY_PREFIX + deviceType + ":" + username);
    }

    /**
     * 从 request 的 header 中获取 JWT
     *
     * @param request 请求
     * @return JWT
     */
    public String getJwtFromRequest(HttpServletRequest request) {
        String bearerToken = request.getHeader(StaticConstants.TOKEN_HEADER);
        if (StrUtil.isNotBlank(bearerToken) && bearerToken.startsWith(StaticConstants.TOKEN_PREFIX)) {
            return bearerToken;
        }
        return null;
    }

    /**
     * 根据 request 获取用户名
     *
     * @param request 请求
     * @return 用户名
     */
    public String getUsernameFromRequest(HttpServletRequest request) {
        String jwt = getJwtFromRequest(request);
        return getUsernameFromJWT(jwt);
    }

    /**
     * 根据 jwt 获取用户名
     *
     * @param jwt JWT
     * @return 用户名
     */
    public String getUsernameFromJWT(String jwt) {
        Claims claims = parseJWT(jwt);

        return claims.getSubject();
    }

    /**
     * 根据 request 获取用户信息
     *
     * @param request 请求
     * @return 用户信息
     */
    public User getUserFromRequest(HttpServletRequest request) {
        String jwt = getJwtFromRequest(request);
        return getUserFromJWT(jwt);
    }

    /**
     * 根据 jwt 获取用户信息
     *
     * @param jwt JWT
     * @return 用户信息
     */
    public User getUserFromJWT(String jwt) {
        Claims claims = parseJWT(jwt);
        return Convert.convert(User.class, claims.get("user"));
    }

    /**
     * 根据 request 获取角色
     *
     * @param request 请求
     * @return 用户角色
     */
    public List<String> getRolesFromRequest(HttpServletRequest request) {
        String jwt = getJwtFromRequest(request);
        return getRolesFromJWT(jwt);
    }

    /**
     * 根据 jwt 获取角色
     *
     * @param jwt JWT
     * @return 用户角色
     */
    public List<String> getRolesFromJWT(String jwt) {
        Claims claims = parseJWT(jwt);
        return Convert.convert(new TypeReference<List<String>>() {}, claims.get("roles"));
    }

    /**
     * 根据 request 获取Authorities
     *
     * @param request 请求
     * @return Authorities
     */
    public Collection<? extends GrantedAuthority> getAuthoritiesFromRequest(HttpServletRequest request) {
        String jwt = getJwtFromRequest(request);
        return getAuthoritiesFromJWT(jwt);
    }

    /**
     * 根据 jwt 获取Authorities
     *
     * @param jwt JWT
     * @return Authorities
     */
    public Collection<? extends GrantedAuthority> getAuthoritiesFromJWT(String jwt) {
        List<String> rolesFromJWT = getRolesFromJWT(jwt);
        return rolesFromJWT.stream()
                .map(JwtUtil::role2SimpleGrantedAuthority)
                .collect(Collectors.toList());
    }

    private static SimpleGrantedAuthority role2SimpleGrantedAuthority(String roleCode){
        return new SimpleGrantedAuthority(roleCode);
    }
}
